package com.xu.cloud.common.auth;

import com.xu.cloud.common.aop.ServletRequestPointcut;
import com.xu.cloud.common.aop.XuApiPointcut;
import com.xu.cloud.common.exception.ErrorCodeEnum;
import com.xu.cloud.common.exception.XuCloudException;
import com.xu.cloud.common.util.HttpServletUtil;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.aop.Pointcut;
import org.springframework.aop.framework.autoproxy.AbstractBeanFactoryAwareAdvisingPostProcessor;
import org.springframework.aop.support.ComposablePointcut;
import org.springframework.aop.support.DefaultPointcutAdvisor;
import org.springframework.aop.support.annotation.AnnotationMatchingPointcut;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.lang.Nullable;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.CollectionUtils;

import java.util.List;
import java.util.Objects;

/**
 * 认证aop拦截器
 *
 * @author xuguan
 * @since 2024/9/24
 */
@Configuration
public class AuthenticationCheckAdvisingBeanPostProcessor extends AbstractBeanFactoryAwareAdvisingPostProcessor {

    public static final Logger log = LoggerFactory.getLogger(AuthenticationCheckAdvisingBeanPostProcessor.class);

    private final AuthenticationProperties properties;

    public AuthenticationCheckAdvisingBeanPostProcessor(AuthenticationProperties properties) {
        this.properties = properties;
    }

    @Override
    public void setBeanFactory(BeanFactory beanFactory) {
        super.setBeanFactory(beanFactory);

        final Pointcut cpc = new AnnotationMatchingPointcut(Authentication.class, true);
        final Pointcut mpc = new AnnotationMatchingPointcut(null, Authentication.class, true);
        final Pointcut requestPc = new ServletRequestPointcut();
        final Pointcut xuApiPc = new XuApiPointcut();
        final ComposablePointcut composablePointcut = new ComposablePointcut(cpc).union(mpc).union(requestPc).union(xuApiPc);

        final AuthenticationCheckMethodInterceptor interceptor = new AuthenticationCheckMethodInterceptor();
        interceptor.setUrlPatterns(properties.getUrlPatterns());
        this.advisor = new DefaultPointcutAdvisor(composablePointcut, interceptor);
    }

    private static class AuthenticationCheckMethodInterceptor implements MethodInterceptor {

        private List<String> urlPatterns;

        public List<String> getUrlPatterns() {
            return urlPatterns;
        }

        public void setUrlPatterns(List<String> urlPatterns) {
            this.urlPatterns = urlPatterns;
        }

        @Override
        public Object invoke(MethodInvocation methodInvocation) throws Throwable {
            final Authentication methodAuthorization = AnnotationUtils.findAnnotation(methodInvocation.getMethod(), Authentication.class);
            final Authentication typeAuthorization = AnnotationUtils.findAnnotation(methodInvocation.getThis().getClass(), Authentication.class);
            // 方法上注解优先级高于类上注解
            Authentication authorization = Objects.nonNull(methodAuthorization)
                    ? methodAuthorization
                    : typeAuthorization;

            if (Objects.nonNull(authorization)) {
                checkAuthentication();
            }

            if (matchUri(urlPatterns)) {
                checkAuthentication();
            }

            return methodInvocation.proceed();
        }

        private void checkAuthentication() {
            // 认证
            final AuthContext authContext = AuthContextHolder.getAuthContext();
            if (Objects.isNull(authContext)) {
                throw new XuCloudException(ErrorCodeEnum.UNAUTHENTICATED.getCode(), ErrorCodeEnum.UNAUTHENTICATED.getMessage());
            }
        }

        private boolean matchUri(@Nullable List<String> urlPatterns) {
            if (CollectionUtils.isEmpty(urlPatterns)) {
                return false;
            }

            final AntPathMatcher antPathMatcher = new AntPathMatcher();
            final String requestUri = HttpServletUtil.currentRequest().getRequestURI();
            for (String urlPattern : urlPatterns) {
                if (antPathMatcher.match(urlPattern, requestUri)) {
                    return true;
                }
            }
            return false;
        }
    }
}

